package algorithms.nchance;
import storage.Block;
import configuration.Configuration;
import simulator.Client;
import simulator.Manager;
import java.util.LinkedHashMap;
import java.util.Random;
import java.util.Map;
import java.util.Collections;
/**
* @author Nodirjon Siddikov
*/
public class NChanceClient extends Client
{
//state of local cache
//key - local cache block ID (different than disk block ID)
//value - local cache block content
private LinkedHashMap<Integer, LocalCacheBlock> _lclCache; //client's local cache information
private NChanceManager _manager;
//constructor to construct the client
public NChanceClient(int id)
{
super(id);
_lclCacheSize = Configuration.localCacheSize; //in KB
//calculate the max number of cache blocks
//that local cache memory can accommodate
int lclCacheBlockSize = Configuration.tagSize + Configuration.blockSize;
_maxNoOfCacheBlocks = _lclCacheSize / lclCacheBlockSize;
//calculate the hashtable capacity
int hashTableCapacity = (int)Math.ceil(_maxNoOfCacheBlocks/_hashTableLoadFactor) + 1;
//constructs linkedHashMap and its order of iteration is
//from least accessed to most accessed (access-order)
//access order is managed by linked list
_lclCache = new LinkedHashMap<Integer, LocalCacheBlock>(hashTableCapacity, _hashTableLoadFactor, true)
{
private static final long serialVersionUID = 1L;
// (an anonymous inner class)
//this method is fired everytime put() method is called
@Override
protected boolean removeEldestEntry(Map.Entry<Integer, LocalCacheBlock> eldestEntry)
{
//check current size with initial size
//size() - current size
//_maxNoOfCacheBlocks - maximum allowed size
if(size() > _maxNoOfCacheBlocks)
{
//this will handle what to do with to be removed cache block
handleRemovedCacheBlock(eldestEntry.getValue());
//true = cache block will be removed
return true;
}
//false = cache block will NOT be removed
return false;
}
};
if(Configuration.detailedStats != 0)
{
System.out.println("N-Chance client " + getID() + " is up.");
System.out.println("Local cache size: " + _lclCacheSize + " Bytes.");
System.out.println("Max no. of cache blocks: " + _maxNoOfCacheBlocks);
}
}
public void setManager(Manager m)
{
_manager = (NChanceManager)m;
}
//receive a read request from an application
public Block receiveReadRequest(int diskBlockID)
{
if(Configuration.detailedStats != 0)
{
System.out.println();
System.out.println("Client " + getID() + " is requesting block " + diskBlockID);
}
//STEP 1://///////////////////////////////////////////////////////////////////////////////////
//reset the block access time
_blckAccessTime = 0;
_ttlNoOfReadRequests++;
LocalCacheBlock lclCacheBlock = null;
//search the local cache memory
for(Map.Entry<Integer, LocalCacheBlock> e: _lclCache.entrySet())
{
if(e.getValue().getDiskBlockID() == diskBlockID)
{
//found
//make sure GET method is fired!!
lclCacheBlock = _lclCache.get(e.getKey());
//stop looping and break
break;
}
}
//increment the block access time
_blckAccessTime += Configuration.cacheAccessTime;
//if the block is cached locally
if(lclCacheBlock != null)
{
//increment local cache hit
_lclCacheHitCount++;
_ttlBlockAccessTime += _blckAccessTime;
if(Configuration.detailedStats != 0)
{
System.out.println("Local cache hit.");
System.out.println("Block access time: " + _blckAccessTime);
}
//if recirculation count is set (i.e. singlet)
if(lclCacheBlock.getRecirulationCount() != -1)
{
//cache block is just referenced, so reset the cache block's recirculation count
lclCacheBlock.resetRecirculationCount();
}
//serve the disk block
return lclCacheBlock.getDiskBlock();
}
//STEP 2://///////////////////////////////////////////////////////////////////////////////
//if the block is not cached locally
//increment the local cache miss
_lclCacheMissCount++;
if(Configuration.detailedStats != 0)
{
System.out.println("Local cache miss.");
}
//contact manager to get info of remote client that has the block
NChanceClient rmtClient = _manager.lookUpGlobalCache(diskBlockID);
_lookUpCount++;
_blckAccessTime += (Configuration.messageLatency + Configuration.cacheAccessTime);
Block rmtDiskBlock = null;
//if remote client with diskBlock exists
if(rmtClient != null)
{
//get the remote disk block
rmtDiskBlock = rmtClient.serveDiskBlock(diskBlockID);
_blckAccessTime += (Configuration.messageLatency + Configuration.cacheAccessTime);
if(rmtDiskBlock != null)
{
_rmtCacheHitCount++;
_blockReadCount++;
_ttlBlockAccessTime += _blckAccessTime;
if(Configuration.detailedStats != 0)
{
System.out.println("Remote cache hit.");
System.out.println("Disk block is fetched from client " + rmtClient.getID());
System.out.println("Block access time: " + _blckAccessTime);
}
//cache the disk block
cacheDiskBlock(rmtDiskBlock, -1);
//update the manager table
_manager.addDiskBlock(this.getID(), rmtDiskBlock.getID());
_consistencyCount++;
_digestExchangeCount++;
return rmtDiskBlock;
}
}
//STEP 3:///////////////////////////////////////////////////////////////////////////////////
//increment the remote cache miss
_rmtCacheMissCount++;
if(Configuration.detailedStats != 0)
{
System.out.println("Remote cache miss.");
}
rmtDiskBlock = _manager.readFromStorage(diskBlockID);
_lookUpCount++;
_blckAccessTime += (2 * Configuration.messageLatency + Configuration.storageAccessTime); // client -> mgr -> storage
if(rmtDiskBlock != null)
{
_serverDiskHitCount++;
_ttlBlockAccessTime += _blckAccessTime;
if(Configuration.detailedStats != 0)
{
System.out.println("Block access time: " + _blckAccessTime);
}
//disk block is read from file server
cacheDiskBlock(rmtDiskBlock, -1);
//update the manager's forwarding table
_manager.addDiskBlock(this.getID(), rmtDiskBlock.getID());
_consistencyCount++;
_digestExchangeCount++;
return rmtDiskBlock;
}
return null;
}
//helper class to handle with removed cache block
private void handleRemovedCacheBlock(LocalCacheBlock block)
{
boolean isSinglet = false;
//if recirculation count was NOT set
if(block.getRecirulationCount() == -1)
{
//check if the block is the singlet
if(_manager.checkForSinglet(block.getDiskBlockID()))
{
isSinglet = true;
//set the recirculation count to 2
block.resetRecirculationCount();
}
else //if the block is NOT singlet
{
isSinglet = false;
}
_consistencyCount++;
}
else //if the recirculation count was set, i.e. cache block was a SINGLET
{
if(block.getRecirulationCount() > 0)
{
isSinglet = true;
//decrement the recirculation count
block.decrementRecirculationCount();
}
else //recirculation count == 0
{
isSinglet = false;
if(Configuration.detailedStats != 0)
{
System.out.println("Previously singlet disk block " + block.getDiskBlockID() + " become NON-singlet.");
}
}
}
if(isSinglet)
{
//relocate the disk block
if(Configuration.detailedStats != 0)
{
System.out.println("Singlet disk block " + block.getDiskBlockID() + " is being relocated. Recirculation count = " + block.getRecirulationCount());
}
relocate(block.getDiskBlock(), block.getRecirulationCount());
}
else //either block is NOT singlet OR its recirculation count == 0
{
//it gets removed from the manager
_manager.removeDiskBlock(this.getID(), block.getDiskBlockID());
_consistencyCount++;
_digestExchangeCount++;
if(Configuration.detailedStats != 0)
{
System.out.println("Non-singlet or recirculation count == 0 disk block " + block.getDiskBlockID() + " is removed completely from client " + this.getID());
}
}
}
private void printLocalCacheContent()
{
System.out.print("Client " + this.getID() +"'s local cache content: ");
for(LocalCacheBlock b: _lclCache.values())
{
System.out.print(b.getDiskBlockID() + " ");
}
System.out.println();
}
public Block serveDiskBlock(int diskBlockID)
{
//look up local cache
for(LocalCacheBlock b: _lclCache.values())
{
if(b.getDiskBlockID() == diskBlockID)
{
return b.getDiskBlock();
}
}
return null;
}
//cache the disk block
private void cacheDiskBlock(Block diskBlock, int reCrclCount)
{
//cache the disk block
LocalCacheBlock lclCacheBlock =
new LocalCacheBlock(new Block(diskBlock.getID(), diskBlock.getSize()));
//set the recirculation count
lclCacheBlock.setRecirculationCount(reCrclCount);
//get the maximum cache block ID
int newCacheBlockID = 0;
if(!_lclCache.isEmpty())
{
newCacheBlockID = Collections.max(_lclCache.keySet()) + 1;
}
//cache the local block
_lclCache.put(newCacheBlockID, lclCacheBlock);
if(Configuration.detailedStats != 0)
{
System.out.println("Client " + this.getID() + " locally cached the disk block " + diskBlock.getID());
printLocalCacheContent();
}
}
//relocate the singlet cache block to other peer
private void relocate(Block block, int reCrclCount)
{
//randomly select a remote client
Random prng = new Random();
int destClientID;
do
{
destClientID = prng.nextInt(Configuration.noOfClients);
}
while(destClientID == this.getID());
//ask manager to send the block other client
_manager.acceptRelocateRequest(this.getID(), destClientID, block, reCrclCount);
_replacementCount++;
_relocationCount++;
}
//modified replacement algorithm
public void modifiedReplacementAlgorithm(Block block, int reCrclCount)
{
if(_lclCache.size() < _maxNoOfCacheBlocks)
{
cacheDiskBlock(block, reCrclCount);
_relocationCount++;
}
else
{
//delete oldest duplicated (non-singlet) block
for(Map.Entry<Integer, LocalCacheBlock> e: _lclCache.entrySet())
{
boolean isSinglet = _manager.checkForSinglet(e.getValue().getDiskBlockID());
_consistencyCount++;
//an entry with lowest index is the oldest in LinkedHashmap
if(!isSinglet)
{
//remove the entry
_lclCache.remove(e.getKey());
//it gets removed from the manager
_manager.removeDiskBlock(this.getID(), e.getValue().getDiskBlockID());
_consistencyCount++;
_digestExchangeCount++;
if(Configuration.detailedStats != 0)
{
System.out.println("Non-singlet disk block " + e.getValue().getDiskBlockID() + " is removed completely from client " + this.getID());
}
cacheDiskBlock(block, reCrclCount);
_relocationCount++;
return;
}
}
//delete oldest recirculating block
for(Map.Entry<Integer, LocalCacheBlock> e: _lclCache.entrySet())
{
//remove the entry
_lclCache.remove(e.getKey());
//it gets removed from the manager
_manager.removeDiskBlock(this.getID(), e.getValue().getDiskBlockID());
_consistencyCount++;
_digestExchangeCount++;
if(Configuration.detailedStats != 0)
{
System.out.println("No non-singlet block is found.");
System.out.println("Singlet disk block " + e.getValue().getDiskBlockID() + " is removed completely from client " + this.getID());
}
cacheDiskBlock(block, reCrclCount);
return;
}
}
}
//used for calculation of communication overhead for client (in and out of Bytes)
public double getDigestExchangeFraction()
{
return _digestExchangeCount * Configuration.globalCacheBlockSize;
}
public double getBlockReadFraction()
{
return _blockReadCount * Configuration.blockSize;
}
public double getRelocationFraction()
{
return _relocationCount * Configuration.blockSize;
}
public double getHintExchangeFraction()
{
return 0;
}
}